#pragma once
#ifdef WIN32
#pragma warning( disable : 4290 )
#endif
#include <glad\glad.h>
#include <string>
#include <map>
#include <glm/glm.hpp>
#include <stdexcept>
#include <memory>

enum ShaderType {
    VERTEX = GL_VERTEX_SHADER,
    FRAGMENT = GL_FRAGMENT_SHADER,
    GEOMETRY = GL_GEOMETRY_SHADER,
    TESS_CONTROL = GL_TESS_CONTROL_SHADER,
    TESS_EVALUATION = GL_TESS_EVALUATION_SHADER,
    COMPUTE = GL_COMPUTE_SHADER
};
enum ShaderDataType {
    FLOAT = GL_FLOAT,
    FLOAT2 = GL_FLOAT_VEC2,
    FLOAT3 = GL_FLOAT_VEC3,
    FLOAT4 = GL_FLOAT_VEC4,
    INT = GL_INT,
    UINT = GL_UNSIGNED_INT,
    DOUBLE = GL_DOUBLE,
    BOOL = GL_BOOL,
    MAT2 = GL_FLOAT_MAT2,
    MAT3 = GL_FLOAT_MAT3,
    MAT4 = GL_FLOAT_MAT4
};
enum Usage {
    STATIC_COPY = GL_STATIC_COPY,
    STATIC_DRAW = GL_STATIC_DRAW,
    STATIC_READ = GL_STATIC_READ,
    DYNAMIC_COPY = GL_DYNAMIC_COPY,
    DYNAMIC_DRAW = GL_DYNAMIC_DRAW,
    DYNAMIC_READ = GL_DYNAMIC_READ,
    STREAM_COPY = GL_STREAM_COPY,
    STREAM_DRAW = GL_STREAM_DRAW,
    STREAM_READ = GL_STREAM_READ,
    DYNAMIC_STORAGE_BIT = GL_DYNAMIC_STORAGE_BIT
};

class GLSLProgram {
private:
    GLuint handle;
    bool linked;
    std::map<std::string, int> uniformLocations;

    GLint getUniformLocation(const char* name);
    void detachAndDeleteShaderObjects();
    bool fileExists(const std::string& fileName);
    std::string getExtension(const char* fileName);

public:
    GLSLProgram();
    ~GLSLProgram();

    GLSLProgram(const GLSLProgram&) = delete;
    GLSLProgram& operator=(const GLSLProgram&) = delete;

    void compileShader(const char* fileName);
    void compileShader(const char* fileName, ShaderType type);
    void compileShader(const std::string& source, ShaderType type, const char* fileName = NULL);

    void link();
    void validate();
    void use();

    int getHandle();
    bool isLinked();

    void bindAttribLocation(GLuint location, const char* name);
    void bindFragDataLocation(GLuint location, const char* name);

    void setUniform(const char* name, float x, float y, float z);
    void setUniform(const char* name, const glm::vec2& v);
    void setUniform(const char* name, const glm::vec3& v);
    void setUniform(const char* name, const glm::vec4& v);
    void setUniform(const char* name, const glm::mat4& m);
    void setUniform(const char* name, const glm::mat3& m);
    void setUniform(const char* name, float val);
    void setUniform(const char* name, int val);
    void setUniform(const char* name, bool val);
    void setUniform(const char* name, GLuint val);

    void findUniformLocations();
    void printActiveUniforms();
    void printActiveUniformBlocks();
    void printActiveAttribs();
    const char* getTypeString(GLenum type);
};
